package com.wzapp.sitposture.config.redission.aop;

import com.wzapp.sitposture.config.redission.annotation.RedisLock;
import com.wzapp.sitposture.config.redission.annotation.RedisLockKey;
import com.wzapp.sitposture.config.redission.expression.ExpressionEvaluator;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

/**
 * 功能: <br/>
 *
 * @author ql
 * @create 2018-11-13 17:25
 **/
@Component
@Aspect
@Slf4j
public class RedisLockAspect {
    @Autowired
    private RedissonClient redissonClient;
    private ExpressionEvaluator<String> evaluator = new ExpressionEvaluator<>();

    @Around("@annotation(com.wzapp.sitposture.config.redission.annotation.RedisLock)")
    public Object lock(ProceedingJoinPoint point) throws Throwable {
        RLock lock = null;
        Object object = null;
        try {
            RedisLock redisLock = getDistRedisLockInfo(point);
            String lockKey = getLockKey(point, redisLock.key());
            String value = redisLock.value();
            if (!StringUtils.isEmpty(value)){
                lockKey=lockKey+getValue(point, value);
                System.out.println("lockKey==="+lockKey);
            }
//            if (null != HeaderUtil.getUserIdFromPrincipal()) {
//                lockKey = lockKey + HeaderUtil.getUserIdFromPrincipal();
//            }
            log.info("线程ID：" + Thread.currentThread().getId()+"=======================info====="+lockKey);
            lock = redissonClient.getLock(lockKey);
            if (lock != null) {
                Boolean status = lock.tryLock(redisLock.maxSleepMills(), redisLock.keepMills(), TimeUnit.MILLISECONDS);
                log.info("线程ID：" + Thread.currentThread().getId()+"=======================锁成功====="+status);
                if (status) {
                    object = point.proceed();
                } else {
//                    log.info("线程ID：" + Thread.currentThread().getId()+"======================= 锁了  请重新尝试");
                }
            }else {
                log.info("线程ID：" + Thread.currentThread().getId()+"=======================锁了请重新尝试=====");
            }
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
        return object;
    }

    /**
     * 获取分布式锁
     *
     * @param point
     * @return
     */
    private RedisLock getDistRedisLockInfo(ProceedingJoinPoint point) {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        return method.getAnnotation(RedisLock.class);
    }

    /**
     * 获取包括方法参数上的key
     * redis key的拼写规则为 "DistRedisLock+" + key + @DistRedisLockKey<br/>
     *
     * @param point
     * @param lockKey
     * @return
     */
    public static String getLockKey(ProceedingJoinPoint point, String lockKey) {
        try {
            lockKey = "DistRedisLock:" + lockKey;
            Object[] args = point.getArgs();
            if (args != null && args.length > 0) {
                MethodSignature methodSignature = (MethodSignature) point.getSignature();
                Annotation[][] parameterAnnotations = methodSignature.getMethod().getParameterAnnotations();
                SortedMap<Integer, String> keys = new TreeMap<>();
                for (int i = 0; i < parameterAnnotations.length; i++) {
                    RedisLockKey redisLockKey = getAnnotation(RedisLockKey.class, parameterAnnotations[i]);
                    if (redisLockKey != null) {
                        Object arg = args[i];
                        if (arg != null) {
                            keys.put(redisLockKey.order(), arg.toString());
                        }
                    }
                }
                if (keys != null && keys.size() > 0) {
                    for (String key : keys.values()) {
                        lockKey += key;
                    }
                }
            }

            return lockKey;
        } catch (Exception e) {
            log.error("getLockKey error.", e);
        }
        return null;
    }

    /**
     * 获取注解类型
     *
     * @param annotationClass
     * @param annotations
     * @param <T>
     * @return
     */
    private static <T extends Annotation> T getAnnotation(final Class<T> annotationClass, final Annotation[] annotations) {
        if (annotations != null && annotations.length > 0) {
            for (final Annotation annotation : annotations) {
                if (annotationClass.equals(annotation.annotationType())) {
                    return (T) annotation;
                }
            }
        }
        return null;
    }

    private String getValue(JoinPoint joinPoint, String condition) {
        return getValue(joinPoint.getTarget(), joinPoint.getArgs(),
                joinPoint.getTarget().getClass(),
                ((MethodSignature) joinPoint.getSignature()).getMethod(), condition);

    }

    private String getValue(Object object, Object[] args, Class clazz, Method method,
                          String condition) {
        if (args == null) {
            return null;
        }
        EvaluationContext evaluationContext =
                evaluator.createEvaluationContext(object, clazz, method, args);
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
        return evaluator.condition(condition, methodKey, evaluationContext, String.class);
    }

    private void test(JoinPoint point) throws ClassNotFoundException {
        String targetName = point.getTarget().getClass().getName();
        String simpleName = point.getTarget().getClass().getSimpleName();
        String methodName = point.getSignature().getName();
        Object[] arguments = point.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String key = "";
        String[] paramNames = {};
        for(Method method:methods){
            if(method.getName().equals(methodName)){
                key = method.getAnnotation(RedisLock.class).value();
                System.out.println("key is "+key);
                paramNames = getParamterNames(method);
            }
        }

        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(key);
        EvaluationContext context = new StandardEvaluationContext();
        for(int i=0;i<arguments.length;i++){
            context.setVariable(paramNames[i],arguments[i]);
        }
        System.out.println("=========================="+expression.getValue(context,String.class));
    }

    public String[] getParamterNames(Method method){

        LocalVariableTableParameterNameDiscoverer u =
                new LocalVariableTableParameterNameDiscoverer();
        return  u.getParameterNames(method);
    }
}
